home *** CD-ROM | disk | FTP | other *** search
Wrap
/* This file demonstrates some slightly sophisticated drawing, as well as basic use of files in MacStarter. To use this file, you should add it to the MacStarter.π project and delete the current applicationProcs file. The file inputBoxes.c must also be present in the MacStarter.π project window. When using this program, the user draws in a window using the mouse. The curve traced by the mouse is drawn, along with its seven "reflections", giving a pretty(?), symmetric pattern. Sketches can be saved to files and reloaded. All places where this file has been modified (from the original applicationProcs.c) are commented with comments that begin with // */ #include "globals-MacStarter.h" #include "inputBoxes.h" long gEventWaitTime = 100000; MenuHandle editMenu, fileMenu; void InitApplication(void); void UpdateMenus(void); void DoEditMenu(int itemNum); void DoFileMenu(int itemNum, int* done); void DoOtherMenu(int menuID, int itemNum); void ApplicationIdle(void); void CleanUpApplication(void); void AboutBox(void); void DoNewCommand(void); // define a symbolic constant specifying the maximum number of points // on the curves drawn by the user in a single window. #define maxPoints 1000 short WindowCt = 0; // to keep track of the number of windows opened // by the program; see function DoNewCommand below. void doOpen(void); // Declare a function to carry out Open Command class myWindow : public xWindow { public: // (Note: not all this stuff needs to be public; I am being lazy here. // It was easiest to make everything public when I kept getting // "access denied" errors, rather that improve the design. This is // not so bad in a small program that doesn't have to be maintained, // I guess.) // define the data that is relevant to the contents of this particular window: short ptCount; // how many points have been drawn (0 to maxPoints) short allFilledUp; // this is initialized to 0 and is set to 1 when // the number of points first reaches maxPoints // (It exists so that I can avoid giving the // user more than one "all filled up" message // per window.) Point pt[maxPoints]; // A list of the points drawn by the user. As the // user moves the mouse, a list of points traversed // is kept. The curve that is drawn is just // obtained by connecting these points. // The special value of pt[i].h = -1 separates // sequences of points on different curves. short winWidth,winHeight; // When the window changes size, the points in // array pt have to be scaled to the new size // these variables store the width and height // by which the points are curently scaled. // (These values are changed in function // adjustToNewSize, where the scaling is done.) void doClear(void); // Function to respond to Clear command void doSave(void); // Function to respond to Save command. // (Note: I put doClear and doSave inside the // window definition because they are directed at // a particular window. The commands New and // Open are not directed to a particular window, // so their handlers are not part of the window // definition.) void PutLineSegment(Point pt1, Point pt2); // Draws a line from pt1 to pt2 along with the seven // possible "reflections" of the line. virtual void OpenInRect(Str255 title, int left, int top, int right, int bottom); virtual short Close(void); virtual void adjustToNewSize(void); virtual void SetDefaults(void); virtual void doKey(char ch); virtual void doContentClick(Point localPt); virtual void doRedraw(Rect* badRect); virtual void doHScroll(int dh); virtual void doVScroll(int dv); virtual void doActivate(int active); }; // Function doClear is called in response to a Clear command from the // edit menu. It just clears the window by declaring that there are no // points for the window. NOTE the use of ForceRedraw(0), which causes the // window to be erased and redrawn by the functions with those specific // jobs. This is generally preferable to doing the redrawing "in place", // since that can lead to unnecessary code duplication. void myWindow::doClear(void) { ptCount = 0; allFilledUp = 0; ForceRedraw(0); } // Function doSave is called in response to the Save command from the // file menu. It uses function OpenNewFile (defined in inputBoxes.c) // to select the new file's name and folder using a standard Mac new file // dialog. (I should probably do more error checking here but in fact, // an error is unlikely.) void myWindow::doSave(void) { FILE *file; Str255 title; short i; GetTitle(title); if ( file = OpenNewFile("\pSave sketch in:",title) ) { SetTitle(title); fprintf(file, "%hd %hd %hd\n", winWidth, winHeight, ptCount); for (i=0; i<ptCount; i++) fprintf(file, "%hd %hd\n", pt[i].h, pt[i].v); fclose(file); }; } // Function doOpen is called in response to the Save command from the // file menu. It used function OpenOldFile (defined in inputBoxes.c) // to allow the user to specify the file to be read. // I do a fair amount of error-checking here because it is possible // for the user to select any text file, and if the file is not one // produced previously by this program, it will almost certainly generate // an error. void doOpen(void) { FILE *file; Str255 title; short width,height,ptCt; short h,v; short i; myWindow *win; if ( file = OpenOldFile(title) ) { win = new myWindow; win->Open(title); // note that the window title is the file name // that is returned by OpenOldFile if (fscanf(file,"%hd %hd %hd\n", &width, &height, &ptCt) != 3 || width <= 0 || height < 0 || ptCt > maxPoints) { TellUser("\pSorry, an error occured while trying to read from the file."); win->Close(); fclose(file); return; } else { win->winHeight = height; win->winWidth = width; win->ptCount = ptCt; }; for (i=0; i<win->ptCount; i++) { if (fscanf(file, "%hd %hd\n", &h, &v) != 2 || h < -1 || v < 0 || v>height || h>width ) { TellUser("\pSorry, illegal data or end-of-file encountered while trying to read from the file."); win->Close(); fclose(file); return; } else { win->pt[i].h = h; win->pt[i].v = v; } }; fclose(file); win->adjustToNewSize(); } } void myWindow::SetDefaults(void) { inherited::SetDefaults(); features = hasGoAway + hasZoom + hasGrow; // modify the feature list; // I don't want any scroll bars, but I do want a close box, // zoom box and grow box ptCount = 0; // initialize window data; there are initially no points allFilledUp = 0; // and the window is NOT allFilledUp. minH = 100; // set minimum window size when user drags "grow box" minV = 100; } void myWindow::OpenInRect(Str255 title, int left, int top, int right, int bottom) { inherited::OpenInRect(title,left,top,right,bottom); } short myWindow::Close(void) { inherited::Close(); } void myWindow::doKey(char ch) { } // Function PutLineSegment is used by doRedraw and by doContentClick to // draw a line segment and its seven possible "reflections" (using horizontal, // vertical and diagonal reflection). (I put reflections in quotes since // the diagonal "reflection" is not really a true reflection if the window // is not square.) This function assumes that the drawing port is already // set to be this window. void myWindow::PutLineSegment(Point pt1, Point pt2) { short a,b,x,y; a = pt1.h; // first, plot the line itself and its horizontal, b = pt1.v; // vertical and combined horizontal/vertical reflection x = pt2.h; y = pt2.v; MoveTo(a,b); LineTo(x,y); MoveTo(winWidth-a,b); LineTo(winWidth-x,y); MoveTo(winWidth-a,winHeight-b); LineTo(winWidth-x,winHeight-y); MoveTo(a,winHeight-b); LineTo(x,winHeight-y); a = pt1.v * ((double) winWidth )/ winHeight; // next, compute the diagonal b = pt1.h * ((double) winHeight )/ winWidth; // "reflection" of the original x = pt2.v * ((double) winWidth )/ winHeight; // line and plot it and its y = pt2.h * ((double) winHeight )/ winWidth; // reflections MoveTo(a,b); LineTo(x,y); MoveTo(winWidth-a,b); LineTo(winWidth-x,y); MoveTo(winWidth-a,winHeight-b); LineTo(winWidth-x,winHeight-y); MoveTo(a,winHeight-b); LineTo(x,winHeight-y); } void myWindow::doContentClick(Point localPt) { Point lastPt, nextPt; short didSomeDrawing; if (allFilledUp) // drawing after allFilledUp is not allowed return; lastPt = localPt; // starting point of curve didSomeDrawing = 0; while (StillDown()) { // continue while the user holds down the mouse button GetMouse(&nextPt); // read mouse location if (nextPt.h < 0) // clip mouse location to make sure it is in window nextPt.h = 0; else if (nextPt.h > winWidth) nextPt.h = winWidth; if (nextPt.v < 0) nextPt.v = 0; else if (nextPt.v > winHeight) nextPt.v = winHeight; if ( (nextPt.h-lastPt.h > 1) // test if the new point is || (nextPt.h-lastPt.h < -1) // not too close to last point; || (nextPt.v-lastPt.v > 1) // this avoids storing || (nextPt.v-lastPt.v < -1) ) { // too many points if (ptCount == maxPoints) { TellUser("\pEach window can only have a certain amount of data. This window's data space is now completely filled. You can't do any more drawing in it."); allFilledUp = 1; // out of room; stop drawing return; }; pt[ptCount++] = lastPt; // record a point in data array PutLineSegment(lastPt,nextPt); // and draw the line and reflections lastPt = nextPt; didSomeDrawing = 1; // remember that at least one point was recorded }; }; if (didSomeDrawing && ptCount < maxPoints) pt[ptCount++].h = -1; // if any points were stored, put a sentinel // in the array to separate this curve from the // next one the user might draw (so that they // are not connected when the window is redrawn) } void myWindow::doRedraw(Rect* badRect){ short i; if (ptCount > 0) // redraw any curves entered previously by user for (i=1; i<ptCount; i++) if (pt[i].h != -1 && pt[i-1].h != -1) // an h value of -1 is not PutLineSegment(pt[i-1],pt[i]); // actually on a curve; this is // a sentinel value between curves } void myWindow::adjustToNewSize(void) { Rect oldRect,newRect; short i; inherited::adjustToNewSize(); // Rescale the points to fit correctly into the newly resized window; // be careful not to scale the sentinel points with h = -1 as these // are not really points, but are only there to separate data for // different curves. (NOTE: because coordinates are integers, // the scaling done here involves some rounding errors, which // introduces some distortions into the image.) SetRect(&oldRect,0,0,winWidth,winHeight); SetRect(&newRect,0,0,theWindow->portRect.right - 1,theWindow->portRect.bottom - 1); // These rectangles are required for the Mac toolbox function ScalePt // which is used to do the scaling. oldRect is the old window rectangle, // and newRect is the window rectangle after resizing. (The size of the // old rectangle has to be remembered in the window data variables // winHeight and winWidth.) for (i=0; i<ptCount; i++) { // scale the points if (pt[i].h != -1) ScalePt(pt+i, &oldRect, &newRect); }; winWidth = newRect.right; // record current window size winHeight = newRect.bottom; } void myWindow::doHScroll(int dh) { inherited::doHScroll(dh); } void myWindow::doVScroll(int dv) { inherited::doVScroll(dv); } void myWindow::doActivate(int active) { inherited::doActivate(active); } void InitApplication(void) { MenuHandle appleMenu; fileMenu = GetMHandle(2); editMenu = GetMHandle(3); appleMenu = GetMHandle(1); SetItem(appleMenu,1,"\pAbout KaleidoSketch..."); // Program name for Apple Menu SetItem(fileMenu,1,"\pNew"); // Replaces "New Window" in file menu // (Seems better for a program that uses files.) InsMenuItem(fileMenu, "\pSave/S;Open/O", 1); // Add two file-oriented commands as entries #2 and 3 in file // menu; (Note that you could use ResEdit to build menus.) // Note that the item numbers for Quit and Close command // are changed, which forces some changes in functions // UpdateMenus and DoFileMenu. DoNewCommand(); } void UpdateMenus(void) { short i; WindowPtr win; xWindow *xwin; win = FrontWindow(); if ( win && ((WindowPeek)win)->windowKind < 0 ) { EnableItem(editMenu,1); for (i=3; i<7; i++) EnableItem(editMenu,i); } else { DisableItem(editMenu,1); for (i=3; i<6; i++) // #6 ("Clear") is handled below DisableItem(editMenu,i); } if (win && xWindow::Window2XWindow(win,&xwin)) { // the frontmost window is one belonging to this program, so I want // to enable the commands that are window-directed, such as Close, // and possibly Clear and Save (if the window is not empty). EnableItem(fileMenu,4); // Close Window Command // The next line checks whether anything has been drawn in the front // window. The type-cast (myWindow*) is required to get access to // ptCount, since the compiler knows only that xwin is an xWindow. if ( ((myWindow*)xwin)->ptCount > 0 ) { EnableItem(fileMenu,3); // Save Command EnableItem(editMenu,6); // Clear Command } else { // front window is empty; nothing to save or clear DisableItem(fileMenu,3); DisableItem(editMenu,6); } } else { // No front window to close, save, or clear DisableItem(fileMenu,3); DisableItem(fileMenu,4); DisableItem(editMenu,6); } } void DoEditMenu(int itemNum) { // having decided to use the Clear command from the Edit menu in my // program, I have to handle it here. Note again the use of type- // casting, which is necessary because Window2XWindow returns an // xWindow, not a myWindow. // Note: The test in the first line should always be true; If UpdateMenus // is correct, the Clear command will be disabled unless it is appropriate xWindow *xwin; if ( itemNum == 6 && xWindow::Window2XWindow(FrontWindow(),&xwin) ) ((myWindow*)xwin)->doClear(); } void DoFileMenu(int itemNum, int* done) { // This function has been modified to reflect the new commands (Save and // Open) added to the file menu in InitApplication. xWindow *win; if (itemNum == 6) // changed number of quit command from 4 to 6 *done = 1; else if (itemNum == 1) DoNewCommand(); else if (itemNum == 2) doOpen(); else if (itemNum == 3 && xWindow::Window2XWindow(FrontWindow(),&win)) ((myWindow*)win)->doSave(); else if (itemNum == 4 && xWindow::Window2XWindow(FrontWindow(),&win)) win->Close(); } void DoOtherMenu(int menuID, int itemNum) { } void ApplicationIdle(void) { } void CleanUpApplication(void) { } void AboutBox(void) { // The strings in the call to ParamText have been modified to describe // this program. (Again, this is not a secure way of identifying your // program, as noted in the comments on file applicationProcs.c.) ParamText( "\pKaleidoSketch", "\pDavid Eck", "\pHobart and William Smith Colleges\rGeneva, NY 14456\rE-mail: eck@hws.bitnet", "\pThis program was written in THINK C to illustrate the use of a Macintosh application shell that I wrote."); Alert(128,0L); } void DoNewCommand(void) { // This function has been modified to keep track of how many windows have // been opened and to name them appropriately: Untitled 1, Untitled 2, ... // There are some tricks here because win->open(title) requires a // Pascal style string, but it is easiest to produce the title using // sprintf, which makes a C style string. This requires the conversion // CtoPstr and the type-cast of title in the call to sprintf. // This type-cast is required because Str255 is type (unsigned char)* // and sprintf requires char* (I think). // (By the way, sprintf requires that this file include stdio.h, which is // done indirectly through the header inputBoxes.h so it is not necessary // to include it explcitely.) myWindow *win; Str255 title; win = new myWindow; WindowCt++; sprintf((char*)title,"Untitled %i",WindowCt); CtoPstr(title); win->Open(title); }